//
//  JJChatViewController.swift
//  swiftProject
//
//  Created by yangjian on 2021/11/26.
//

import UIKit
import HyphenateChat
import RxSwift

class JJSingleChatVC: UIViewController {

    
    var bag = DisposeBag()
    var appearBag = DisposeBag()
    
    var conversation : EMConversation!
    var tempNewMsgModels = [MsgCellBaseModel]()
    var tempNewLock = NSObject()
    
    
    var models = [MsgCellBaseModel]()
    
    var hasMoreEarlyMessages = true
    
//    var tableViewConstraintHeight : MASConstraint!
    let tableViewWidth : CGFloat = screen_width()
    var tableViewHeight : CGFloat = 0
    var tableViewCurrentHeight : CGFloat = 0
    //保持在底部的阈值
    let keepBottomThreshold : CGFloat = 100
    //展示滚动至底部的阈值
    let showScrollToBottomThreshold : CGFloat = 500

    //获取更早消息的阈值//和tableViewHeader高度一致
//    let loadEarlyThreshold : CGFloat = 20
    
    //是否正在加载更早的消息
    var loadingEarlyData = false
    
    let tableViewHeader = JJChatMessageTableViewHeader.init()
    let tableView = UITableView.init(frame: CGRect.zero, style: .plain)
    let chatBar = JJChatBar.init()
    let populatedView = UIView.init()
    
    let recordVoiceView = JJChatBarVoiceView()
    let emojiPutView = JJChatBarEmojiPutView()
    let actionsView = JJChatBarActionsView()
    
    //底部弹出相关记录
    var oldPanelType = JJChatBarPanelType.none
    var newPanelType = JJChatBarPanelType.none

    //正在加载最新消息
    var loadingNewData = false;
    //消息列表显示时间记录
    var newMsgTimestamp : Int64 = 0

    var firstMsgid : String? = nil
    
//    var keepBottom = true
    
    var keepBottom = true
    var dragging = false
    
    init(conversationid : String,chatType:EMConversationType) {
        super.init(nibName: nil, bundle: nil)
        self.hidesBottomBarWhenPushed = true
        self.conversation = EMClient.shared().chatManager!.getConversation(conversationid, type: EMConversationType(rawValue: chatType.rawValue)!, createIfNotExist: true)
//        self.helper = JJSingleChatHelper.init(conversationid: conversationid,chatType: chatType,viewController: self)
        IMChatVCManager.share.currentChatVC = self
        IMChatVCManager.share.currentChatType = .single
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        self.config()
    }
        
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.willAppear()
    }
//    override func viewDidAppear(_ animated: Bool) {
//        super.viewDidAppear(animated)
//        print("viewDidAppear")
//    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.willDisappear()
    }
    
//    override func viewDidDisappear(_ animated: Bool) {
//        super.viewDidDisappear(animated)
//        print("viewDidDisappear")
//    }
    
    func config(){
        self.view.backgroundColor = UIColor.white
        self.tableViewHeight =
        SCREEN_HEIGHT
        - navigation_top(navigationController: self.navigationController)
        - safearea_bottom()
        - 50
        print("|\(screen_height())|\(navigation_top(navigationController: self.navigationController))|\(safearea_bottom())|")
        self.tableViewCurrentHeight = self.tableViewHeight
        self.configChatBar()
        self.configTableView()
        self.firstLoadData()
        self.configInitObservable()
    }
    
    func configChatBar(){
        self.navigationController?.navigationBar.barTintColor = UIColor.lightText
        self.navigationController?.navigationBar.backgroundColor = UIColor.brown
        
        self.view.addSubview(self.chatBar)
        self.view.addSubview(self.populatedView)
        self.view.addSubview(self.recordVoiceView)
        self.view.addSubview(self.emojiPutView)
        self.view.addSubview(self.actionsView)

        self.chatBar.snp.makeConstraints { make in
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.height.greaterThanOrEqualTo(50)
        }

        self.populatedView.snp.makeConstraints { make in
            make.top.greaterThanOrEqualTo(self.chatBar.snp.bottom)//?.priority()(MASLayoutPriority.init(rawValue: 1000))
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.bottom.equalTo(self.view)
            make.height.equalTo(safearea_bottom())
        }
        self.recordVoiceView.snp.makeConstraints { make in
            make.top.greaterThanOrEqualTo(self.chatBar.snp.bottom)
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.bottom.equalTo(200)
            make.height.equalTo(200)
        }
        self.emojiPutView.snp.makeConstraints { make in
            make.top.greaterThanOrEqualTo(self.chatBar.snp.bottom)
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.bottom.equalTo(300 + safearea_bottom())
            make.height.equalTo(300)
        }
        self.actionsView.snp.makeConstraints { make in
            make.top.greaterThanOrEqualTo(self.chatBar.snp.bottom)
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.bottom.equalTo(chatBarActionsViewHeight + safearea_bottom())
            make.height.equalTo(chatBarActionsViewHeight)
        }
        self.populatedView.backgroundColor = UIColor.white
        self.recordVoiceView.backgroundColor = UIColor.brown
        self.actionsView.backgroundColor = UIColor.blue
        
    }
    
    func configTableView(){
        self.view.addSubview(self.tableView)
        self.tableView.snp.makeConstraints { make in
            make.top.equalTo(navigation_top(navigationController:self.navigationController))
            make.left.equalTo(self.view)
            make.right.equalTo(self.view)
            make.width.equalTo(tableViewWidth)
//            self.tableViewConstraintHeight =
            make.height.equalTo(tableViewHeight).priority(50)
            make.bottom.equalTo(self.chatBar.snp.top)
        }
        self.tableView.separatorStyle = .none
        self.tableView.contentInsetAdjustmentBehavior = .never
        self.tableView.tableHeaderView = self.tableViewHeader
        self.tableViewHeader.snp.makeConstraints { make in
            make.width.equalTo(tableViewWidth)
            make.height.equalTo(tableViewHeader.headerHeight)
        }
        let cellNames : [AnyClass] = [
            MsgUserTextCell.classForCoder(),
            MsgUserVoiceCell.classForCoder(),
            MsgUserImageCell.classForCoder(),
            MsgUserVideoCell.classForCoder(),
            MsgUserLocationCell.classForCoder(),
            MsgUserNameCardCell.classForCoder(),
            MsgUserBigEmojiCell.classForCoder(),
            MsgUserFileCell.classForCoder(),
            MsgUserUnknownCell.classForCoder(),
            MsgSystemCell.classForCoder(),
            MsgTimeMarkerCell.classForCoder(),
        ]
        print(String(NSStringFromClass(cellNames[0])))
        for cellName in cellNames {
            self.tableView.register(cellName,forCellReuseIdentifier: NSStringFromClass(cellName).components(separatedBy: ".").last!)
        }
        self.tableView.delegate = self
        self.tableView.dataSource = self
    }

    
    
    
    //判断是否保持至底部.(当有新消息插入时需要)
    func keepBottomComp() {
        keepBottom = (self.tableView.contentSize.height - self.tableView.contentOffset.y - self.tableViewCurrentHeight) > keepBottomThreshold ? false : true
        print(self.tableView.contentSize.height)
        print(self.tableView.contentOffset.y)
        print(self.tableViewCurrentHeight)
        print(self.tableView.contentSize.height - self.tableView.contentOffset.y - self.tableViewCurrentHeight)
//        keepBottom = keepBottom
//        print(keepBottom)
//        return keepBottom
    }
    func messageCell_allCellModelsHeight() -> CGFloat {
        var allHeight : CGFloat = 0
        for model in self.models {
            allHeight += model.cellHeight
        }
        return allHeight
    }

}

extension JJSingleChatVC {
    //根据界面是否存在 init / deinit
    func configInitObservable(){
        
        /*
         某消息状态改变 如果消息有block,不会执行这里.
         添加此代理的原因:
         当一条消息正在发送,此界面被释放
         再次进入界面,消息还处于发送中
         随后消息发送成功,则没有block可更新消息状态.
         由此需要此代理来更正消息状态
         */
        RXEMObservableManager.share.chatObservable.rx_messageStatusDidChange.subscribe(onNext: {[unowned self] message,error in
            for model in self.models {
                if model.message.messageId == message.messageId {
                    model.statusChanged()
                }
            }
        }).disposed(by: bag)
        //某消息已读
        RXEMObservableManager.share.chatObservable.rx_messagesDidRead.subscribe(onNext: {[unowned self] messages in
            for message in messages.reversed() {
                for model in self.models.reversed() {
                    if model.message.messageId == message.messageId {
//                        print("=======原消息:\(message.status.rawValue)--新消息:\(model.message.status.rawValue)")
                        model.statusChanged()
                    }
                }
            }
        }).disposed(by: bag)
        
        //会话已读
        RXEMObservableManager.share.chatObservable.rx_onConversationRead.subscribe(onNext: {[unowned self] from,to in
            if from == self.conversation.conversationId {
                for model in models {
                    model.statusChanged()
                }
            }
        }).disposed(by: bag)
        
        //chatbar相关
        self.chatBar.textView.rx.text.subscribe(onNext: {[unowned self] text in
            if let text {
                print(text)
            }
        }).disposed(by: bag)
      
        self.chatBar.textWillChangeCallBack = {[unowned self] textView,range,text in
            if text == "@" {
                if range.length == 0 {
                    queue_main.asyncAfter(deadline: .now() + 0.1) {
//                        self.selectAt(range: range);
                        IMChatBarMsgAtUtil.selectAt(atHelper: self.chatBar.atHelper,textView: textView, range: range)
                    }
                }
                return true
            }
            if text == "\n" {
//                self.sendTextMessage()
                self.sendMessage(message: IMChatBarMsgAtUtil.generateAtMessage(atHelper: chatBar.atHelper, attributedText: chatBar.textView.attributedText, conversationID: self.conversation.conversationId,chatType: EMChatType(rawValue: conversation.type.rawValue) ?? .chat))
                chatBar.textView.text = ""
                return false
            }
            print(range)
            print(text)
            return true
        }
        
        self.chatBar.rx_panelChanged.subscribe {[unowned self] oldType,newType in
            self.oldPanelType = oldType
            self.newPanelType = newType
            if oldType == .systemKeyboard {
    //            self.chatBar.textView.resignFirstResponder()
                self.view.endEditing(true)
                return
            }
            if newType == .systemKeyboard {
                self.chatBar.textView.becomeFirstResponder()
                return
            }
    //        let keepBottom = self.keepBottom()
            UIView.animate(withDuration: 0.18) {
                self.panelShow(panelType: newType)
                self.populatedView.superview?.layoutIfNeeded()
                if self.keepBottom && !self.tableView.isDragging {
                    self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: false)
                }
            } completion: { finish in
            }
        }.disposed(by: bag)
        
        self.chatBar.rx_textHeightChanged.subscribe(onNext: {[unowned self] textHeight in
            print("chatbar高度变了")
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {[unowned self] in
                self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: false)
            }
        }).disposed(by: bag)
        
        self.recordVoiceView.rx_recordVoiceFinished.subscribe(onNext: {[unowned self] amrPath,duration in
            if duration < 1 {
                print("录制时间太短,中断发送消息")
                return
            }
            let body = EMVoiceMessageBody.init(localPath: amrPath, displayName: amrPath.components(separatedBy: "/").last)
            body.duration = Int32(duration)
            let message = EMChatMessage(conversationID: self.conversation.conversationId, body: body, ext: nil)
            self.sendMessage(message: message)
        }).disposed(by: bag)

        
        self.chatBar.textView.rx.didBeginEditing.subscribe(onNext:  { () in
            print("键盘弹出,开始输入")
        }).disposed(by: bag)
        self.chatBar.textView.rx.didEndEditing.subscribe(onNext: {() in
            print("键盘收起,结束输入")
        }).disposed(by: bag)
        
        self.actionsView.rx_selectedItem.subscribe(onNext: {[unowned self] id in
            if id == 0 {
                self.selectImages()
            }else if id == 1 {
            }else if id == 2 {
            }else if id == 3 {

            }
        }).disposed(by: self.bag)
        
        
        
    }
    //根据界面是否显示来添加代理,如果界面没显示(但是存在,这个时候代理失效) viewWillAppear / viewWillDisappear
    func configAppearObservable(){
        //收到新消息
        RXEMObservableManager.share.chatObservable.rx_messagesDidReceive.subscribe(onNext: {[unowned self] messages in
            let currentConversationReceivedMessages = messages.filter({return $0.conversationId == self.conversation.conversationId })
            self.appendNewMsgToBufferArea(messages: currentConversationReceivedMessages)
            queue_main.async {
                self.startReloadNewData()
            }
        }).disposed(by: appearBag)
        

        NotificationCenter.default.rx
            .notification(UIResponder.keyboardWillShowNotification).take(until: self.rx.deallocated)
            .subscribe(onNext: {[unowned self] notification in
                let userInfo = notification.userInfo!
                let frameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue
                let height = frameValue.cgRectValue.height
                let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber
                let duration = durationValue.floatValue
//                let keepBottom = self.keepBottom
                UIView.animate(withDuration: TimeInterval(duration)) {
                    self.populatedView.snp.updateConstraints { make in
                        make.height.equalTo(height)
                    }
                    self.panelHideAll()
                    self.populatedView.superview?.layoutIfNeeded()
//                    if self.keepBottom && !self.tableView.isDragging {//杨剑标记,这里会闪退,还需要排查
//                    }
                    self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: false)
                    self.keepBottom = true
                }completion: { finish in
                    self.tableViewCurrentHeight = self.tableViewHeight - (height - safearea_bottom())
                }
                self.chatBar.didShowSystemKeyborad()
            }).disposed(by: self.appearBag)
        
        NotificationCenter.default.rx
            .notification(UIResponder.keyboardWillHideNotification).take(until: self.rx.deallocated)
            .subscribe(onNext: {[unowned self] notification in
                let userInfo = notification.userInfo!
                print(userInfo)
                let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber
                let duration = durationValue.floatValue
//                let keepBottom = self.keepBottom()
                UIView.animate(withDuration: TimeInterval(duration)) {
                    self.populatedView.snp.updateConstraints { make in
                        make.height.equalTo(safearea_bottom())
                    }
                    self.panelShow(panelType: self.newPanelType)
                    self.populatedView.superview?.layoutIfNeeded()
                    if self.keepBottom && !self.tableView.isDragging {
                        self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: false)
                    }
                }completion: { finish in
                }
            }).disposed(by: self.appearBag)
        
        
    }
}

//第一次加载数据
extension JJSingleChatVC {
    //第一次加载数据
    func firstLoadData(){
        //获取数据
        let count : Int32 = 20
        self.conversation.loadMessagesStart(fromId: nil, count: count, searchDirection: .up){messages , error in
            if error != nil {
                print("\(String(describing: error))")
                self.hasMoreEarlyMessages = false
                self.reloadTableViewFirstLoadData()
                return
            }
            guard let messages = messages else {
                self.hasMoreEarlyMessages = false
                self.reloadTableViewFirstLoadData()
                return
            }
            self.firstFillModelsInList(messages: messages)
            if messages.count < count {
                self.hasMoreEarlyMessages = false
            }else{
                self.hasMoreEarlyMessages = true
            }
            self.reloadTableViewFirstLoadData()
        }
    }
    func reloadTableViewFirstLoadData(){
        print("这是第一次加载消息!!!回调!!!")
        if !self.hasMoreEarlyMessages {
            self.tableViewHeader.noMoreData()
        }else{
            self.tableViewHeader.normal()
        }
        if self.models.count > 0 {
            self.tableView.reloadData()
            self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: false)
        }
    }
}
//appear处理逻辑
extension JJSingleChatVC {
    //界面显示时处理逻辑
    func willAppear(){
        print("viewWillAppear")
        self.appearBag = DisposeBag()
        configAppearObservable()
        
        //获取数据之前处理下已读状态
        if self.conversation.unreadMessagesCount > 0 {
            var error : EMError? = nil
            self.conversation.markAllMessages(asRead: &error)
            EMClient.shared().chatManager?.ackConversationRead(self.conversation.conversationId,completion: { error in
            })
//            EMIMHelper.share.updateUnreadMessageCount()
        }
    }
    //界面不显示时处理逻辑
    func willDisappear(){
        print("viewWillDisappear")
        //标签视图隐藏
//        JJMessageCellMenuManager.shared().hideMenu()
        //关闭底部视图
        self.chatBar.hideAllPanelExceptionSystemKeyboard()
        //播放
        JJVoicePlayHelper.share.stop(error: nil)
        //转文字
        JJVoiceConvertWordHelper.share.cancelAllTask()
        self.appearBag = DisposeBag()
        EMIMHelper.share.updateUnreadMessageCount()
    }
}



//加载更多更早消息
extension JJSingleChatVC {
    //加载更多更早的消息.
    func loadMoreEarlyData(){
        if loadingEarlyData {
            return
        }
        if !self.hasMoreEarlyMessages {
            return
        }
        self.loadingEarlyData = true
        self.tableViewHeader.launch()
        print("加载更早消息================")
        
//        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {

        RunLoop.current.perform(inModes: [.default]) {[unowned self] in
            let count : Int32 = 20
            
            self.conversation.loadMessagesStart(fromId: self.firstMsgid, count: count, searchDirection: .up){messages , error in
                if error != nil {
                    print("\(String(describing: error))")
                    self.hasMoreEarlyMessages = false
                    return
                }
                guard let messages = messages else {
                    self.hasMoreEarlyMessages = false
                    return
                }
//                var index = 0
                let earlyHeight = self.fillEarlyModelsInList(messages: messages)
                if messages.count < count {
                    self.hasMoreEarlyMessages = false
                }else{
                    self.hasMoreEarlyMessages = true
                }
                if !self.hasMoreEarlyMessages {
                    self.tableViewHeader.noMoreData()
                }else{
                    self.tableViewHeader.normal()
                }
                self.reloadTableViewLoadEarlyData(earlyHeight: earlyHeight)
                self.loadingEarlyData = false
            }
        }
//        }

    }

    func reloadTableViewLoadEarlyData(earlyHeight : CGFloat){
        if earlyHeight > 0 {
            let allHeight = self.messageCell_allCellModelsHeight()
            let oldHeight = allHeight - earlyHeight
            self.tableView.reloadData()
            print("ccccc\(allHeight)")
            print("ddddd\(self.tableView.contentSize.height)")
            //分以下情况:
            //1.加载前后的高度都将小于视图窗口高度.
            if allHeight < self.tableViewHeight {
                //不做任何无感偏移
                print("加载完成!!!!!!!!!!!!!!!!")
            }else if oldHeight < self.tableViewHeight {        //2.加载前小于视图窗口,加载后却大于视图窗口
                //无感偏移 -> 底部
                //调整至底部方法为:将最新的高度去除窗口高度为应该做的偏移量
                self.tableView.setContentOffset(CGPoint(x: 0,y: allHeight - self.tableViewHeight),
                                                animated: false)
                print("加载完成!!!!!!!!!!!!!!!!")
            }else {
                //3.contentSize都很大,超出窗口
                //无感偏移 -> 当前窗口展示的位置
                //在顶部添加的高度为 :
                //当前偏移量 :
                print("eeeee\(self.tableView.contentOffset.y)")
                let earlyOffsetY = earlyHeight - self.tableView.contentOffset.y
                self.tableView.setContentOffset(CGPoint(x: 0, y: earlyOffsetY), animated: false)
                print("加载完成!!!!!!!!!!!!!!!!")
            }
        }
    }

}

//底部弹出功能栏等处理
extension JJSingleChatVC {
    
    func panelHideAll(){
        self.tableViewCurrentHeight = self.tableViewHeight
        self.recordVoiceView.snp.updateConstraints { make in
            make.bottom.equalTo(200)
        }
        self.emojiPutView.snp.updateConstraints { make in
            make.bottom.equalTo(300)
        }
        self.actionsView.snp.updateConstraints { make in
            make.bottom.equalTo(chatBarActionsViewHeight)
        }
    }
    
    func panelShow(panelType: JJChatBarPanelType){
        IMMsgCellMenuManager.share.hideMenu()
        switch (panelType) {
        case .voice:
//            let permission = AVAudioSession.sharedInstance().recordPermission
//            AVAudioSessionRecordPermission permissionStatus = [[AVAudioSession sharedInstance] recordPermission];
//            if (permissionStatus == AVAudioSessionRecordPermissionDenied) {
//                error = [NSError errorWithDomain:@"未开启麦克风权限" code:-1 userInfo:nil];
//                break;
//            }
            self.tableViewCurrentHeight = self.tableViewHeight - (200 - safearea_bottom())
            self.recordVoiceView.snp.updateConstraints { make in
                make.bottom.equalTo(0)
            }
            self.emojiPutView.snp.updateConstraints { make in
                make.bottom.equalTo(300)
            }
            self.actionsView.snp.updateConstraints { make in
                make.bottom.equalTo(chatBarActionsViewHeight + safearea_bottom())
            }
            break
        case .emoji:
            self.tableViewCurrentHeight = self.tableViewHeight - (300 - safearea_bottom())
            self.recordVoiceView.snp.updateConstraints { make in
                make.bottom.equalTo(200)
            }
            self.emojiPutView.snp.updateConstraints { make in
                make.bottom.equalTo(0)
            }
            self.actionsView.snp.updateConstraints { make in
                make.bottom.equalTo(chatBarActionsViewHeight + safearea_bottom())
            }
            break
        case .functions:
            self.tableViewCurrentHeight = self.tableViewHeight - (250 - safearea_bottom())
            self.recordVoiceView.snp.updateConstraints { make in
                make.bottom.equalTo(200)
            }
            self.emojiPutView.snp.updateConstraints { make in
                make.bottom.equalTo(300)
            }
            self.actionsView.snp.updateConstraints { make in
                make.bottom.equalTo(0)
            }
            break
        default:
            self.tableViewCurrentHeight = self.tableViewHeight
            self.recordVoiceView.snp.updateConstraints { make in
                make.bottom.equalTo(200)
            }
            self.emojiPutView.snp.updateConstraints { make in
                make.bottom.equalTo(300)
            }
            self.actionsView.snp.updateConstraints { make in
                make.bottom.equalTo(chatBarActionsViewHeight + safearea_bottom())
            }
            break
        }
    }
    
}


//新消息
extension JJSingleChatVC {
    //当有新消息,需要下列两个方法结合使用,其中reloadNewMessage 为递归
    func startReloadNewData(){
        if loadingNewData {
            return
        }
        print("+++++++++++++++++[1]")
//        objc_sync_enter(loadingNewData)
        loadingNewData = true
//        objc_sync_exit(loadingNewData)
        self.reloadTableViewNewData()
    }
    
    func reloadTableViewNewData(){
        print("+++++++++++++++++[2]")

//        objc_sync_enter(loadingNewData)
        if self.tempNewMsgModels.count == 0 {
            loadingNewData = false
            return
        }
//        objc_sync_exit(loadingNewData)
        print("+++++++++++++++++[3]")
        objc_sync_enter(self.tempNewLock)
//        let keep = keepBottom()
//        print(keep)
        models += tempNewMsgModels
        tempNewMsgModels.removeAll()
        objc_sync_exit(self.tempNewLock)
        //需要在刷新界面之前获取之前的content高度等等数据,从而得到是否需要保持滚动底部
        //刷新界面
        tableView.reloadData()
        print("+++++++++++++++++[4]")

        //判断是否需要滚动到底部
        if self.keepBottom && !self.tableView.isDragging {
            //追加新消息刷新,逻辑已经移动到scrollViewDidEndScrollingAnimation
//            self.reloadTableViewNewData()
            self.tableView.performBatchUpdates {
                self.tableView.scrollToRow(at: IndexPath.init(row: self.models.count - 1, section: 0), at: .bottom, animated: true)
            }completion: { finished in
                self.reloadTableViewNewData()
            }
            print("+++++++++++++++++[5]")

        }else{
            print("+++++++++++++++++[100]")
            self.reloadTableViewNewData()
        }
    }
    

}

//tableview的代理处理以及滚动处理
extension JJSingleChatVC : UITableViewDelegate,UITableViewDataSource{
    
    @objc func tableViewHeaderRefreshClick(){
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        IMMsgCellMenuManager.share.hideMenu()
//        JJMessageCellMenuManager.shared().hideMenu()
//        print("正在滚动\(scrollView.contentOffset.y)")
//        print("xxxxx\(self.tableView.contentSize.height)")
        //如果没有更早数据,则完全不需要加载.直接毙掉
        if hasMoreEarlyMessages {
            let offsetY = CGFloat(scrollView.contentOffset.y)
            //加载更早消息行为
//            if offsetY < self.tableViewHeader.headerHeight{
            if offsetY < -1{
                if loadingEarlyData {
                    return
                }
                self.loadMoreEarlyData()
            }
        }
        
        //展示移至底部按钮
    }
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        print("scrollViewWillBeginDragging")
        self.view.resignFirstResponder()
        self.chatBar.hideAllPanel()
    }
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
       
        print("===========================================")
//
//        let keepBottom = (scrollView.contentSize.height - scrollView.contentOffset.y - self.tableViewCurrentHeight) > keepBottomThreshold ? false : true
//        print(self.tableView.contentSize.height)
//        print(self.tableView.contentOffset.y)
//        print(self.tableViewCurrentHeight)
//        print(self.tableView.contentSize.height - self.tableView.contentOffset.y - self.tableViewCurrentHeight)
//
//        print(keepBottom)
//        print("===========================================")
//        if self.loadingNewData {
//            print("+++++++++++++++++[6]")
//        }
//        print("scrollViewDidEndScrollingAnimation")
    }
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        keepBottomComp()
        print("scrollViewDidEndDecelerating")
    }
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        keepBottomComp()
        print("scrollViewDidEndDragging")
    }
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return self.models[indexPath.row].cellHeight
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return self.models[indexPath.row].cellHeight
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.models.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let model = self.models[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: model.cellName, for: indexPath) as! MsgBaseCell
        cell.bindFromModel(model)
//        if cell is MsgUserBaseCell {
//            (cell as! MsgUserBaseCell).headTapGesture.rx.event.subscribe(onNext: {[unowned self] sender in
//                
//            }).disposed(by: cell.bag)
//        }
        cell.currentTableView = tableView
        cell.currentIndexPath = indexPath
        
        //这里必须绑定cell的reuseBag,每次重用时释放掉,重新设定,否则释放不掉,重复添加,会出问题
        cell.rx_msgEventSubject.subscribe(onNext : {[unowned self] model,view,event,text in
            print(view)
            print(model)
            print(event)
            print(text)
            print("=================")
            if event == .messageTap {//消息点击
                self.messageCellTap(model: model)
            }else if event == .messageLongTap {//消息长按
                self.messageCellLongTap(model: model)
            }else if event == .messageMenuConvertText {//菜单栏语音转文字
                model.startVoickConvertText()
            }else{
                
            }
        }).disposed(by: cell.reuseBag)
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.chatBar.hideAllPanel()
    }
}


let messageCell_allMenuItemModels = [
    IMMsgCellMenuModel(id: 0,title: "复制"),
    IMMsgCellMenuModel(id: 1,title: "转发"),
    IMMsgCellMenuModel(id: 2,title: "收藏"),
    IMMsgCellMenuModel(id: 3,title: "删除"),
    IMMsgCellMenuModel(id: 4,title: "多选"),
    IMMsgCellMenuModel(id: 5,title: "引用"),
    IMMsgCellMenuModel(id: 6,title: "转文字"),
]

extension JJSingleChatVC{
    //cell的menu
    func messageCellLongTap(model : MsgCellBaseModel){
        var ids = [Int]()
        guard let cell = model.currentCell else{
            return
        }
        guard let tableView = cell.currentTableView else{
            return
        }
        if model.msgBodyType == .text {
            ids = [0,1,2,3,4,5]
        }else if model.msgBodyType == .voice {
            ids = [6]
        }
        if ids.count < 1 {
            return
        }
        let list : [IMMsgCellMenuModel] = messageCell_allMenuItemModels.filter { menuModel in
            ids.contains(menuModel.id)
        }
        var items = [IMMsgCellMenuItem]()
        for menuModel in list {
            let item = IMMsgCellMenuItem(id: menuModel.id, title: menuModel.title)
            item.rx.controlEvent(.touchUpInside).map { _ in
                return (menuModel.id)
            }.subscribe(onNext:{[unowned self] id in
                print("点击了menu - \(id)")
                IMMsgCellMenuManager.share.hideMenu()
                print(id)
                if id == 6 {
                    model.startVoickConvertText()
                }
            }).disposed(by: bag)
            items.append(item)
        }
        if cell is MsgUserBaseCell {
            IMMsgCellMenuManager.share.showMenu(superView: self.view, targetView: (cell as! MsgUserBaseCell).bubbleView, items: items)
        }else{
            IMMsgCellMenuManager.share.showMenu(superView: self.view, targetView: cell, items: items)
        }
    }
    
    func messageCellTap(model : MsgCellBaseModel){
        guard let cell = model.currentCell else{
            return
        }
        if model.msgBodyType == .voice {
            JJVoicePlayHelper.share.play(model: model) { error in
            }
        }else if model.msgBodyType == .custom{
        }else if model.msgBodyType == .text{
        }else if model.msgBodyType == .image{
            self.browseImageOrVideo(model: model)
        }else if model.msgBodyType == .video{
            self.browseImageOrVideo(model: model)
        }else{
        }
    }
}


//发消息
extension JJSingleChatVC{
    //
    func _sendMsg(message : EMChatMessage , completion:@escaping (EMChatMessage?,String,EMError?) -> Void){
        let oldmsgid = message.messageId
        EMClient.shared().chatManager?.send(message, progress: nil , completion: { newMsg, error in
            completion(newMsg,oldmsgid,error)
        })
    }
    
    func sendMessage(message : EMChatMessage){
        print("send===send===0#\(message.messageId)")
        let model = MsgCellBaseModel(message: message)
        _sendMsg(message: message) { newMsg, oldmsgid, error in
            model.statusChanged()
            guard let newMsg else {
                return
            }
            print("send===send===1#\(message.messageId)")
            print("send===send===2#\(newMsg.messageId)")
        }
//        appendNewMsgToBufferArea(messages: [message])
        appendNewModelToBufferArea(model: model)
        queue_main.async {
            self.startReloadNewData()
        }
    }
    
}

//数据源处理方面
extension JJSingleChatVC {
    //直接将数据加入到数据源中(仅首次加载时调用,这里会影响到时间显示问题)
    func firstFillModelsInList(messages : [EMChatMessage]){
        firstMsgid = messages.first?.messageId
        let models = messages.map({MsgCellBaseModel(message: $0)})
        for model in models {
            if msgListRowNeedShowTime(oldTimestamp: self.newMsgTimestamp, newTimestamp: model.message.timestamp){
                self.models.append(generateTimeMsgModel(timestamp: model.message.timestamp))
                self.newMsgTimestamp = model.message.timestamp
            }
            self.models.append(model)
        }
    }
    
    //填充更早消息记录,返回消息高度
    func fillEarlyModelsInList(messages : [EMChatMessage]) -> CGFloat {
        
        firstMsgid = messages.first?.messageId

        var index = 0
        var earlyHeight : CGFloat = 0
        var earlyTimestamp : Int64 = 0
        let earlyModels = messages.map({MsgCellBaseModel(message: $0)})
        for model in earlyModels {
            if msgListRowNeedShowTime(oldTimestamp: earlyTimestamp, newTimestamp: model.message.timestamp){
                let timestampModel = generateTimeMsgModel(timestamp: model.message.timestamp)
                earlyTimestamp = model.message.timestamp
                self.models.insert(timestampModel, at: index)
                index += 1
                earlyHeight += timestampModel.cellHeight
            }
            self.models.insert(model, at: index)
            index += 1
            earlyHeight += model.cellHeight
        }
        return earlyHeight
    }
    //添加消息到缓冲区,如果是新消息,需要使用这里
    func appendNewMsgToBufferArea(messages : [EMChatMessage]){
        let models = messages.map({
            EMClient.shared().chatManager?.sendMessageReadAck($0.messageId, toUser: $0.from)
            self.conversation.markMessageAsRead(withId: $0.messageId, error: nil)
            return MsgCellBaseModel(message: $0)})
        objc_sync_enter(self.tempNewLock)
        for model in models {
            if msgListRowNeedShowTime(oldTimestamp: self.newMsgTimestamp, newTimestamp: model.message.timestamp){
                self.tempNewMsgModels.append(generateTimeMsgModel(timestamp: model.message.timestamp))
                self.newMsgTimestamp = model.message.timestamp
            }
            self.tempNewMsgModels.append(model)
        }
        objc_sync_exit(self.tempNewLock)
    }
    //添加模型到缓冲区,如果是新消息,需要使用这里(前面需要临时持有模型)
    func appendNewModelToBufferArea(model : MsgCellBaseModel){
        objc_sync_enter(self.tempNewLock)
        if msgListRowNeedShowTime(oldTimestamp: self.newMsgTimestamp, newTimestamp: model.message.timestamp){
            self.tempNewMsgModels.append(generateTimeMsgModel(timestamp: model.message.timestamp))
            self.newMsgTimestamp = model.message.timestamp
        }
        self.tempNewMsgModels.append(model)
        objc_sync_exit(self.tempNewLock)
    }
    
    
    
    
}


//以下为 图片/视频 相关
import CLImagePickerTool
import Photos
import MobileCoreServices
import JFHeroBrowser
extension JJSingleChatVC {
    //发送媒体文件消息(传入的是从相册图库中给到的<PHAsset>)
    func sendMediaFileMessage(asset : PHAsset){
        guard let resource = PHAssetResource.assetResources(for: asset).first else {
            print("没有PHAssetResource,也可能是权限问题")
            return
        }
                            
        if let fileExtension = UTTypeCopyPreferredTagWithClass(resource.uniformTypeIdentifier as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue() {
            print("File type is: \(fileExtension)")
        }
        
        let options = PHAssetResourceRequestOptions()
        options.isNetworkAccessAllowed = true
        var fileName = generate_randomString(prefix: "", suffix: "")//生成一个随机的文件名,可不填写
        
        let fileExtension = phassetResource_fetchFileExtension(resource: resource)
        if fileExtension.count > 0 {
            fileName = "\(fileName).\(fileExtension)"
        }
        
//        let path = "\(directory_cacheFiles())/\(fileName)" //不用缓存文件夹,删得有点快...
        let path = "\(directory_files())/\(fileName)"
        let pathUrl = URL(fileURLWithPath: path)
        PHAssetResourceManager.default().writeData(for: resource, toFile: pathUrl, options: options) {[unowned self] error in
            if !file_fileExists(filePath: path) {
                print("文件不存在")
                return
            }
            if asset.mediaType == .image {
                let body = EMImageMessageBody(localPath: path, displayName: fileName)
                if #available(iOS 16, *) {
                    body.size = CGSizeMake(CGFloat(resource.pixelWidth), CGFloat(resource.pixelHeight))
                } else {
                    let data = try? Data.init(contentsOf: pathUrl)
                    body.size = UIImage(data: data ?? Data())?.size ?? CGSizeMake(30, 30)
                }
                let message = EMChatMessage(conversationID: self.conversation.conversationId, body: body, ext: nil)
                self.sendMessage(message: message)
            }else if asset.mediaType == .video {
                let body = EMVideoMessageBody(localPath: path, displayName: fileName)
                if #available(iOS 16, *) {
                    body.thumbnailSize = CGSizeMake(CGFloat(resource.pixelWidth), CGFloat(resource.pixelHeight))
                } else {
                    body.thumbnailSize = video_size(by: pathUrl)
                }
                body.duration = Int32(video_duration(videoURL: pathUrl))
                let message = EMChatMessage(conversationID: self.conversation.conversationId, body: body, ext: nil)
                self.sendMessage(message: message)
            }else if asset.mediaType == .audio {
                let body = EMFileMessageBody(localPath: path, displayName: fileName)
                let message = EMChatMessage(conversationID: self.conversation.conversationId, body: body, ext: nil)
                self.sendMessage(message: message)
            }else{
                let body = EMFileMessageBody(localPath: path, displayName: fileName)
                let message = EMChatMessage(conversationID: self.conversation.conversationId, body: body, ext: nil)
                self.sendMessage(message: message)
            }
        }
    }
    
    //打开相册选取图片(这个功能太简单,用下面那个)
    func __selectImages(){
        // superVC 当前的控制器
        let imagePickTool = CLImagePickerTool()
        imagePickTool.cl_setupImagePickerWith(MaxImagesCount: 6, superVC: self) {[unowned self] (assets,cutImage) in
            print("返回的asset数组是\(assets)")
            print("===")
            for asset in assets {
                self.sendMediaFileMessage(asset: asset)
            }
        }
    }
    //打开相册选取图片
    func selectImages(){
        let minItemSpacing: CGFloat = 2
        let minLineSpacing: CGFloat = 2
        
        // Custom UI
        ZLPhotoUIConfiguration.default()
//            .navBarColor(.white)
//            .navViewBlurEffectOfAlbumList(nil)
//            .indexLabelBgColor(.black)
//            .indexLabelTextColor(.white)
            .minimumInteritemSpacing(minItemSpacing)
            .minimumLineSpacing(minLineSpacing)
            .columnCountBlock { Int(ceil($0 / (428.0 / 4))) }
        
        if ZLPhotoUIConfiguration.default().languageType == .arabic {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .unspecified
        }
        
        // Custom image editor
        ZLPhotoConfiguration.default()
            .editImageConfiguration
//            .imageStickerContainerView(ImageStickerContainerView())
            .canRedo(true)
//            .tools([.draw, .clip, .mosaic, .filter])
//            .adjustTools([.brightness, .contrast, .saturation])
//            .clipRatios([.custom, .circle, .wh1x1, .wh3x4, .wh16x9, ZLImageClipRatio(title: "2 : 1", whRatio: 2 / 1)])
//            .imageStickerContainerView(ImageStickerContainerView())
//            .filters([.normal, .process, ZLFilter(name: "custom", applier: ZLCustomFilter.hazeRemovalFilter)])
        
        /*
         ZLPhotoConfiguration.default()
             .cameraConfiguration
             .devicePosition(.front)
             .allowRecordVideo(false)
             .allowSwitchCamera(false)
             .showFlashSwitch(true)
          */
        ZLPhotoConfiguration.default()
            // You can first determine whether the asset is allowed to be selected.
            .canSelectAsset { _ in true }
            .didSelectAsset { _ in }
            .didDeselectAsset { _ in }
            .noAuthorityCallback { type in
                switch type {
                case .library:
                    debugPrint("No library authority")
                case .camera:
                    debugPrint("No camera authority")
                case .microphone:
                    debugPrint("No microphone authority")
                }
            }
            .gifPlayBlock { imageView, data, _ in
                let animatedImage = FLAnimatedImage(gifData: data)

                var animatedImageView: FLAnimatedImageView?
                for subView in imageView.subviews {
                    if let subView = subView as? FLAnimatedImageView {
                        animatedImageView = subView
                        break
                    }
                }

                if animatedImageView == nil {
                    animatedImageView = FLAnimatedImageView()
                    imageView.addSubview(animatedImageView!)
                }
                animatedImageView?.frame = imageView.bounds
                animatedImageView?.animatedImage = animatedImage
                animatedImageView?.runLoopMode = .default
            }
            .pauseGIFBlock { $0.subviews.forEach { ($0 as? FLAnimatedImageView)?.stopAnimating() } }
            .resumeGIFBlock { $0.subviews.forEach { ($0 as? FLAnimatedImageView)?.startAnimating() } }
//            .operateBeforeDoneAction { currVC, block in
//                // Do something before select photo result callback, and then call block to continue done action.
//                block()
//            }
        
        /// Using this init method, you can continue editing the selected photo
        let ac = ZLPhotoPreviewSheet()
        
//        let ac = ZLPhotoPreviewSheet(selectedAssets: takeSelectedAssetsSwitch.isOn ? selectedAssets : nil)
        
        ac.selectImageBlock = { [weak self] results, isOriginal in
            guard let `self` = self else { return }
//            self.selectedResults = results
//            self.selectedImages = results.map { $0.image }
//            self.selectedAssets = results.map { $0.asset }
//            self.isOriginal = isOriginal
//            self.collectionView.reloadData()
//            debugPrint("images: \(self.selectedImages)")
//            debugPrint("assets: \(self.selectedAssets)")
            debugPrint("isEdited: \(results.map { $0.isEdited })")
            debugPrint("isOriginal: \(isOriginal)")
            
            for result in results {
                self.sendMediaFileMessage(asset: result.asset)
            }

//            guard !self.selectedAssets.isEmpty else { return }
//            self.saveAsset(self.selectedAssets[0])
        }
        ac.cancelBlock = {
            debugPrint("cancel select")
        }
        ac.selectImageRequestErrorBlock = { errorAssets, errorIndexs in
            debugPrint("fetch error assets: \(errorAssets), error indexs: \(errorIndexs)")
        }
        ac.showPhotoLibrary(sender: self)

//            if preview {
//                ac.showPreview(animate: true, sender: self)
//            } else {
//                ac.showPhotoLibrary(sender: self)
//            }
    }
    
    func browseImageOrVideo(model : MsgCellBaseModel){
        //整理数据源
    
        let mediaModels = self.models.filter { cellModel in
            if cellModel.msgBodyType == .image {return true}
            if cellModel.msgBodyType == .video {return true}
            return false
        }
        var index = -1
        
        var list: [HeroBrowserViewModuleBaseProtocol] = []

        var counter = 0
        for cellModel in mediaModels {
            if cellModel.msgBodyType == .image {
                guard let body = model.message.body as? EMImageMessageBody else {
                    continue
                }
                if cellModel.direction == .send {
                    let item = HeroBrowserLocalImageViewModule(image: UIImage(contentsOfFile: body.localPath) ?? UIImage())
                    list.append(item)
                    if cellModel.message.messageId == model.message.messageId {
                        index = counter
                    }
                    counter += 1
                }else {
                    var thumbPath : String? = nil
                    if file_fileExists(filePath: body.thumbnailLocalPath ?? "") {
                        thumbPath = body.remotePath
                    }else{
                        EMClient.shared().chatManager?.downloadMessageThumbnail(cellModel.message, progress: nil)
                    }
                    var path : String? = nil
                    if file_fileExists(filePath: body.localPath) {
                        path = body.remotePath
                    }else{
                        EMClient.shared().chatManager?.downloadMessageAttachment(cellModel.message, progress: nil)
                    }
                    if let path {
                        let item = HeroBrowserNetworkImageViewModule(thumbailImgUrl: thumbPath, originImgUrl: path,provider: HeroNetworkImageProvider.shared)
                        list.append(item)
                        if cellModel.message.messageId == model.message.messageId {
                            index = counter
                        }
                        counter += 1
                    }
                }
            }else if cellModel.msgBodyType == .video {
                guard let body = model.message.body as? EMVideoMessageBody else {
                    continue
                }
                if cellModel.direction == .send {
                    let item = HeroBrowserVideoViewModule(thumbailImgUrl: body.thumbnailLocalPath, fileUrlPath: body.localPath)
                    list.append(item)
                    if cellModel.message.messageId == model.message.messageId {
                        index = counter
                    }
                    counter += 1
                }else {
                    var thumbPath : String? = nil
                    if file_fileExists(filePath: body.thumbnailLocalPath ?? "") {
                        thumbPath = body.remotePath
                    }else{
                        EMClient.shared().chatManager?.downloadMessageThumbnail(cellModel.message, progress: nil)
                    }
                    var path : String? = nil
                    if file_fileExists(filePath: body.localPath) {
                        path = body.remotePath
                    }else{
                        EMClient.shared().chatManager?.downloadMessageAttachment(cellModel.message, progress: nil)
                    }
                    if let path {
                        let item  = HeroBrowserVideoViewModule(thumbailImgUrl: thumbPath, fileUrlPath: path,provider: HeroNetworkImageProvider.shared, autoPlay: false)
                        list.append(item)
                        if cellModel.message.messageId == model.message.messageId {
                            index = counter
                        }
                        counter += 1
                    }
                }
            }else{}
        }
        if index < 0 {
            return
        }
        
        
        
        self.hero.browserMultiSoures(viewModules: list, initIndex: index) {
            [
                .enableBlurEffect(false),
//                    .heroView(button.imageView),
                .imageDidChangeHandle({ [weak self] imageIndex in
                    if let cell = model.currentCell as? MsgUserImageCell {
                        return (cell as MsgUserImageCell).msgImageView
                    }
                    if let cell = model.currentCell as? MsgUserVideoCell {
                        return (cell as MsgUserVideoCell).msgImageView
                    }
                    return model.currentCell?.imageView
//                        guard let self = self else { return nil }
//                        guard let btn = self.view.viewWithTag(imageIndex) as? UIButton else { return nil }
//                        return btn.imageView
                })
            ]
        }

        
        
        
    }
    
    
}
